中間者攻撃を防ぐためのPiping Chunk 0.6.x以降の認証方式の流れ
#Piping_Chunk #セキュリティ #Piping_Server
はじめに
エンドツーエンド暗号化ファイル転送 + 受信者検証 (Piping Chunk 0.4.0) - Piping Server経由でVerification Codeという仕組みが導入され、送信者が受信者が意図している相手かを確かめることが出来るようになった。Verfication Codeの目的は、中間者攻撃をされ、送信者のデータが流出させないこと。
0.6.0で下記の問題を解決した認証方式に変更した。
Piping Chunk 0.4.0 - 0.5.0のVerification Codeの中間者攻撃を成立させる
良いことに、UIは変わらないのでユーザーは使用法はそのままで、安全性が向上する。
Verification Code生成での通信内容が変わるので開発者ツールのNetworkを見ると送り合うJSONが変わっている。(公開情報交換時は暗号化はされない。そもそも暗号化するための交換。)
0.6.x以降の認証方式
全体像を捉えるための説明。
RSAを使っている。
RSAで楕円曲線ディフィー・ヘルマン鍵共有の公開情報を署名する。
RSAの公開鍵のJWKのThumbprint(フィンガープリント)がVerification Codeになっている。
中間者が、楕円曲線ディフィー・ヘルマン鍵共有の公開情報を改ざんしても署名できないことを利用している
基本的なアイデアに関して。Piping Chunk 0.4.0 - 0.5.0のVerification Codeの中間者攻撃を成立させるでは共通鍵生成用の鍵を改ざんを検知できないことが問題だった。それを改善するため署名している。
通信の流れ
流れを書くことで脆弱な箇所があった場合、あとから分かりやすくなること期待。
登場人物は送信者Sと受信者R。
送信者Sと受信者RはRSAのキーペアを生成する
公開鍵をそれぞれ$ Rpub_Sと$ Rpub_Rとする。
秘密鍵ををそれぞれ$ Rpriv_Sと$ Rpriv_Rとする。
コード:https://github.com/nwtgck/piping-chunk-web/blob/4a045b324646e47845f1c4827af959a83b97b467/src/components/PipingChunk.vue#L374
送信者Sと受信者Rは楕円曲線ディフィー・ヘルマン鍵共有用のキーペアを生成する
公開鍵をそれぞれ$ Epub_Sと$ Epub_Rとする。
コード:https://github.com/nwtgck/piping-chunk-web/blob/4a045b324646e47845f1c4827af959a83b97b467/src/components/PipingChunk.vue#L376
送信者Sは$ Rpriv_Sで$ Epub_Sを署名。受信者Rは$ Rpriv_Rで$ Epub_Rを署名する
送信者の署名を$ Sign_S、受信者の署名を$ Sign_Rとする。
コード:https://github.com/nwtgck/piping-chunk-web/blob/4a045b324646e47845f1c4827af959a83b97b467/src/components/PipingChunk.vue#L264-L270
送信者Sは$ Rpub_Sと$ Epub_Sと$ Sign_Sを受信者Rに送る
データ構造:https://github.com/nwtgck/piping-chunk-web/blob/4a045b324646e47845f1c4827af959a83b97b467/src/components/PipingChunk.vue#L188-L199
受信者Rは$ Rpub_Rと$ Epub_Rと$ Sign_Rを送信者Sに送る(上記と同様)
Verification CodeはRSAの公開鍵たちのフィンガープリントを使っている(詳細は以下の手順)
まず、送信者と受信者RSAの公開鍵たちのJWKのThumbprintにする(hex形式)
そのThumbprintを辞書順でソートして'-'で連結する
その連結したものをSHA-256のhex形式で算出する
上位32文字をVerification Codeとして使う
まとめると$ sha256(sort([ thumbprint(Rpub_S), thumbprint(Rpub_R)]).join('-')).take(32) みたいに生成している
ソートする理由:
ソートせずに${送信者}-${受信者}のふうに順番を決めれば一意に決まるのになぜしなかったか?
この鍵交換部分の関数は共通化されていて、送信者も受信者も同じ方法でこの関数を利用する。
つまり、関数は送信者から呼ばれたのか受信者から呼ばれたのかを知らない。知らないほうがシンプルで一貫性があると判断した。
そのためソートして一意に順番が定まるようになっている
コード:https://github.com/nwtgck/piping-chunk-web/blob/4a045b324646e47845f1c4827af959a83b97b467/src/components/PipingChunk.vue#L229-L243
Verification Codeが送信者と受信者で一致しているかを確認して相手が正しいか確認する
一致の確認はPiping Chunkの性質上、通信相手が隣にいたり、自分自身で他の端末に送る場合だったり、電話越しで話してたりするので、それを信用することになる。
$ Epub_Sや$ Epub_Rを使うことで楕円曲線ディフィー・ヘルマン鍵共有で共通鍵は手に入る
その共通鍵でこれ以後の通信は暗号化できる
上記で登場する鍵はいずれもエフェメラルである。使い捨て。
リプレイしようとしても同じ鍵が二度とこないと言える(確率論的に)ので、リプレイできないと思う。
エフェメラルでなくても楕円曲線ディフィー・ヘルマン鍵共有で得られる共通鍵をしれないので、リプレイしても情報を盗み取れないはず。
通信相手の公開鍵のフィンガープリントを使うのはGPGでも使わている方法で信頼いいと思っている。
参考:1分でわかるPGP - 村川猛彦
Piping Chunk 0.4.0 - 0.5.0のVerification Codeの中間者攻撃を成立させるでは$ Epub_Sや$ Epub_Rが中間者に改ざんされる可能性があった。だが、今回の流れではRSAの秘密鍵で$ Epub_Sや$ Epub_Rを署名している。RSAの秘密鍵は通信されないため、中間者は改ざんした楕円曲線ディフィー・ヘルマン鍵共有用の公開鍵を署名することはできない。適当なRSAで署名すればVerification Codeが送信者と受信者で変わるため中間者の存在に気づくことができる。
送信者による許可のなりすまし
Verification Codeの一致ができたら、送信者VERIFYボタンを押すと発生するリクエストに関して。
コード内ではverificationと呼んでいるが、acceptanceとか変更したほうが良さそうな気がしてきた。理由は署名に対する検証のverificationと用語が混同しそうだから。
送信者の許可リクエストは{verified: true}とか{verified: false}なBodyが暗号化されて送信される。これをなりすます問題もある。ただ、これをなりすましてできることは、送信者が許可してないのに送信しようとしていると誤情報を送ったり、許可する予定だったのに先を越して、許可しないという誤情報を流して受信者の受信をあきらめさせること。つまり、データが漏れることとは無関係。中間者攻撃して悪意のあるデータを受信者に流し込むことはできると思う。Verification Codeが違うことがすぐに分かり中間者攻撃に気付けるし、ファイルを開かなければ悪意のあるコードなどが実行されることはないと思う。これらを回避したいなら、受信者にもVERIFYボタンを用意して、VERIFYが押されたあとに受信のGETをし始めるような実装ができると思う。ただ、利便性のため現在はこれを実装しない。情報漏えいが防げていれば問題ないという実装。今後の経験で受信者もVERIFYできる方が良いと経験をすればそういう実装に変わるのだと思う。